﻿// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the LGPL License, Version 3.0. See License.txt in the project root for license information.
// Website: https://admin.blazor.zone

using BlazorAdmin.Interface;
using BootstrapBlazor.Components;
using PetaPoco;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace BlazorAdmin.Services.DbService
{
    /// <summary>
    /// PetaPoco 数据操作扩展类
    /// </summary>
    internal static class DbExtensions
    {
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TModel"></typeparam>
        /// <returns></returns>
        public static Task<List<TModel>> FetchAsync<TModel>(this IDatabase db, QueryPageOptions options)
        {
            Sql sql = db.ProcessQuery<TModel>(options);
            return db.FetchAsync<TModel>(sql);
        }

        public static Task<List<TModel>> FetchAsync<TModel>(this IDatabase db, AppQueryOption query)
        {
            Sql sql = db.ProcessQuery<TModel>(query);
            return db.FetchAsync<TModel>(sql);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TModel"></typeparam>
        /// <returns></returns>
        public static Task<Page<TModel>> PageAsync<TModel>(this IDatabase db, QueryPageOptions options)
        {
            var sql = db.ProcessQuery<TModel>(options);
            return db.PageAsync<TModel>(options.PageIndex, options.PageItems, sql);
        }

        public static Task<Page<TModel>> PageAsync<TModel>(this IDatabase db, AppQueryOption query)
        {
            var sql = db.ProcessQuery<TModel>(query);
            return db.PageAsync<TModel>(query.Option.PageIndex, query.Option.PageItems, sql);
        }

        private static Sql ProcessQuery<TModel>(this IDatabase db, QueryPageOptions options)
        {
            var sql = new Sql();

            // 处理模糊查询
            if (options.Searchs.Any())
            {
                var searchTextSql = new Sql();
                AnalysisExpression(options.Searchs.GetFilterLambda<TModel>(FilterLogic.Or), db, searchTextSql);
                sql.Append(searchTextSql.ToString().Replace("\nAND", "OR"), searchTextSql.Arguments);
            }

            // 处理高级搜索
            if (options.AdvanceSearchs.Any())
            {
                AnalysisExpression(options.AdvanceSearchs.GetFilterLambda<TModel>(), db, sql);
            }

            // 处理自定义搜索与过滤
            var filters = options.Filters.Concat(options.CustomerSearchs);
            if (filters.Any())
            {
                AnalysisExpression(filters.GetFilterLambda<TModel>(), db, sql);
            }

            // 支持多列排序
            var sortName = string.IsNullOrWhiteSpace(options.SortName) ? options.SortName : $"{options.SortName}";
            var sortOrder = options.SortOrder;
            if (!string.IsNullOrEmpty(sortName) && sortOrder != SortOrder.Unset)
            {
                sql.OrderBy(sortOrder == SortOrder.Asc ? sortName : $"{sortName} desc");
            }
            else if (options.SortList != null && options.SortList.Any())
            {
                sql.OrderBy(string.Join(",", options.SortList));
            }
            return sql;
        }

        private static Sql ProcessQuery<TModel>(this IDatabase db, AppQueryOption query)
        {
            Sql sql;
            var singleTableWhere = string.Empty;
            if (!query.Sql.IsSingleTable) //非单表，只能当做结果查询才能支持关联表字段的过滤、排序
            {
                sql = new Sql($"SELECT * FROM ({query.Sql.SQL}) T", query.Sql.Params);
            }
            else //单表直接查询
            {
                var regex = new Regex(@"\s+Where\s+", RegexOptions.IgnoreCase | RegexOptions.Multiline);
                var match = regex.Match(query.Sql.SQL);

                if (match.Success) //提取单表自定义Where
                {
                    sql = new(query.Sql.SQL.Substring(0, match.Index), query.Sql.Params);
                    sql.Where(query.Sql.SQL.Substring(match.Index + match.Value.Length));
                }
                else
                {
                    sql = new Sql(query.Sql.SQL, query.Sql.Params);
                }
            }

            // 处理模糊查询
            if (query.Option.Searchs.Any())
            {
                var searchTextSql = new Sql();
                AnalysisExpression(query.Option.Searchs.GetFilterLambda<TModel>(FilterLogic.Or), db, searchTextSql);
                sql.Append(searchTextSql.ToString().Replace("\nAND", "OR"), searchTextSql.Arguments);
            }

            // 处理高级搜索
            if (query.Option.AdvanceSearchs.Any())
            {
                AnalysisExpression(query.Option.AdvanceSearchs.GetFilterLambda<TModel>(), db, sql);
            }

            // 处理自定义搜索与过滤
            var filters = query.Option.Filters.Concat(query.Option.CustomerSearchs);
            if (filters.Any())
            {
                AnalysisExpression(filters.GetFilterLambda<TModel>(), db, sql);
            }

            // 支持多列排序
            var sortName = string.IsNullOrWhiteSpace(query.Option.SortName) ? query.Option.SortName : $"{query.Option.SortName}";
            var sortOrder = query.Option.SortOrder;
            if (!string.IsNullOrEmpty(sortName) && sortOrder != SortOrder.Unset) //优先手动列排序
            {
                sql.OrderBy(sortOrder == SortOrder.Asc ? sortName : $"{sortName} desc");
            }
            else if (!string.IsNullOrEmpty(query.Sql.OrderBy)) //按代码指定的字段排序
            {
                sql.OrderBy(query.Sql.OrderBy);
            }
            else if (query.Option.SortList != null && query.Option.SortList.Any()) //按组件SortString排序
            {
                sql.OrderBy(string.Join(",", query.Option.SortList));
            }

            return sql;
        }

        private static void AnalysisExpression(Expression expression, IDatabase db, Sql sql)
        {
            switch (expression.NodeType)
            {
                case ExpressionType.Lambda:
                    if (expression is LambdaExpression exp)
                    {
                        AnalysisExpression(exp.Body, db, sql);
                    }
                    break;
                case ExpressionType.AndAlso:
                    if (expression is BinaryExpression andExp)
                    {
                        AnalysisExpression(andExp.Left, db, sql);
                        AnalysisExpression(andExp.Right, db, sql);
                    }
                    break;
                case ExpressionType.OrElse:
                    if (expression is BinaryExpression orExp)
                    {
                        AnalysisExpression(orExp.Left, db, sql);
                        AnalysisExpression(orExp.Right, db, sql);
                    }
                    break;
                case ExpressionType.Call:
                    if (expression is MethodCallExpression callExp)
                    {
                        if (callExp.Method.Name == "Contains")
                        {
                            if (callExp.Object is MemberExpression callLeft)
                            {
                                var callColName = GetColumnName(callLeft.Member) ?? callLeft.Member.Name;
                                var p = (callExp.Arguments[0] as ConstantExpression)?.Value;
                                if (p != null)
                                {
                                    sql.Where($"{db.Provider.EscapeSqlIdentifier(callColName)} like @0", $"%{p}%");
                                }
                            }
                        }
                    }
                    break;
                case ExpressionType.Equal:
                case ExpressionType.NotEqual:
                case ExpressionType.GreaterThan:
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.LessThan:
                case ExpressionType.LessThanOrEqual:
                    if (expression is BinaryExpression binaryExp)
                    {
                        MemberExpression left = null;
                        if (binaryExp.Left is MethodCallExpression methodLeft && methodLeft.Method.Name == "ToString" && methodLeft.Object is MemberExpression p && p.Type.IsEnum)
                        {
                            // 枚举转字符串
                            left = p;
                        }
                        else if (binaryExp.Left is MemberExpression m)
                        {
                            left = m;
                        }

                        if (left != null)
                        {
                            // 查找 PetaPoco.Column 标签
                            var columnName = GetColumnName(left.Member) ?? left.Member.Name;

                            // 查找操作符右侧
                            if (binaryExp.Right is ConstantExpression right)
                            {
                                var v = right.Value;

                                if (v != null)
                                {
                                    var val = v.GetType().IsEnum ? (int)v : v;
                                    var operatorExp = GetOperatorExpression(expression);
                                    sql.Where($"{db.Provider.EscapeSqlIdentifier(columnName)} {operatorExp} @0", val);
                                }
                            }
                        }
                    }
                    break;
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="member"></param>
        /// <returns></returns>
        private static string GetColumnName(MemberInfo member) => member.CustomAttributes
            .FirstOrDefault(i => i.AttributeType == typeof(ColumnAttribute))?.NamedArguments
            .FirstOrDefault(i => i.MemberName == "Name").TypedValue.Value?.ToString();

        private static string GetOperatorExpression(Expression expression) => expression.NodeType switch
        {
            ExpressionType.Equal => "=",
            ExpressionType.NotEqual => "!=",
            ExpressionType.GreaterThan => ">",
            ExpressionType.GreaterThanOrEqual => ">=",
            ExpressionType.LessThan => "<",
            ExpressionType.LessThanOrEqual => "<=",
            _ => ""
        };
    }
}